在程式裡,我們經常會依條件來決定邏輯分支:
ts
CopyEdit
if (isLoggedIn) { ... } else { ... }
而 TypeScript 也提供一種語法,讓「型別」本身可以做類似的判斷,
這就是 條件型別(Conditional Types)。
條件型別讓我們可以根據泛型參數的「型別關係」來決定結果型別,
進而做到更動態、更智慧的型別推導。
條件型別語法:
ts
CopyEdit
T extends U ? X : Y
意思是:
範例:
ts
CopyEdit
type IsString<T> = T extends string ? true : false;
type A = IsString<string>; // true
type B = IsString<number>; // false
條件型別通常與泛型一起使用:
ts
CopyEdit
function printId<T>(id: T) {
type IdType = T extends string ? "Text ID" : "Numeric ID";
let typeName: IdType;
}
這樣可以讓型別在不同輸入時自動變化。
infer
關鍵字:從型別中推導infer
讓我們在條件型別中「提取」一部分型別。
範例:取出陣列元素的型別
ts
CopyEdit
type ElementType<T> = T extends (infer U)[] ? U : never;
type A = ElementType<string[]>; // string
type B = ElementType<number[]>; // number
type C = ElementType<boolean>; // never
當條件型別的泛型參數是 Union 型別 時,它會自動分配到每個成員上:
ts
CopyEdit
type ToArray<T> = T extends any ? T[] : never;
type A = ToArray<string | number>;
// 等同於 string[] | number[]
假設我們有這樣的 API 回應型別:
ts
CopyEdit
type ApiResponse<T> =
| { status: "success"; data: T }
| { status: "error"; error: string };
我們可以用條件型別提取成功資料:
ts
CopyEdit
type ExtractData<T> = T extends { status: "success"; data: infer D } ? D : never;
type UserResponse = ApiResponse<{ id: string; name: string }>;
type UserData = ExtractData<UserResponse>;
// { id: string; name: string }
ts
CopyEdit
type NullableKeys<T> = {
[K in keyof T]: null extends T[K] ? K : never;
}[keyof T];
type User = {
id: string;
name: string | null;
email: string;
};
type Result = NullableKeys<User>; // "name"
我們可以用條件型別讓 API 工具的回傳型別自動推導:
ts
CopyEdit
type Success<T> = { status: "success"; data: T };
type Fail = { status: "error"; error: string };
type ApiResult<T> = Success<T> | Fail;
async function callApi<T>(url: string): Promise<ApiResult<T>> {
const res = await fetch(url);
if (!res.ok) return { status: "error", error: "Failed" };
return { status: "success", data: await res.json() };
}
type ExtractSuccess<T> = T extends { status: "success"; data: infer D } ? D : never;
// 使用時自動推導
const userResult = await callApi<User>("/api/user/1");
type UserData = ExtractSuccess<typeof userResult>;
錯誤 1:忘記 extends
比較的是「可賦值性」
string
extends string | number
→ true錯誤 2:過度巢狀的條件型別
錯誤 3:忽略分配式行為
[...]
包住泛型來避免infer